【Swift UI】@GestureStateの使い方!updating(_:body:)メソッド

【Swift UI】@GestureStateの使い方!updating(_:body:)メソッド

この記事からわかること

  • Swift UI@GestureState使い方
  • ドラッグ(スワイプ)したビュー移動させる方法
  • updating(_:body:)メソッドの使い方

\ アプリをリリースしました /

みんなの誕生日

友達や家族の誕生日をメモ!通知も届く-みんなの誕生日-

posted withアプリーチ

環境

Swift UIでのGestureの実装方法については以下の記事を参考にしてください。

@GestureStateとは?

公式リファレンス:GestureState構造体

Swift UIで使える@GestureStateジェスチャーの状態を管理するためのプロパティラッパーです。このプロパティラッパーを付与することでジェスチャーが変更されるたびに自動的に更新された値をそのプロパティから参照することができます。

@GestureStateを付与したプロパティはGesture.updating(_:body:)メソッドでバインディングすることができるようになるのでセットで使用することが多いです。

@GestureState private var dragOffset: CGFloat = 0

updating(_:body:)メソッド

公式リファレンス:updating(_:body:)メソッド


func updating<State>(
    _ state: GestureState<State>,
    body: @escaping (Self.Value, inout State, inout Transaction) -> Void
) -> GestureStateGesture<Self, State>

updating(_:body:)メソッドはジェスチャーが変更されるたびに呼び出されるクロージャーを提供し、ジェスチャーの状態を更新するために使用されます。引数stateには@GestureStateを付与したプロパティをバインディングし、 ジェスチャーの状態が変化したタイミングで引数bodyのクロージャーが呼び出され、その引数からジェスチャーの値、状態(GestureState)、およびトランザクションを参照することが可能です。

使い方

では@GestureStateを使用してViewをドラッグで移動させるように実装してみます。

ドラッグされたビューを動かすためのポイント

struct DragView: View {
    // Viewを表示させる座標値
    @State private var position = CGPoint(x: 0, y: 0)
    // ドラッグジェスチャー量
    @GestureState private var dragOffset = CGSize.zero
    
    var body: some View {
        Text("Hello")
            .frame(width: 100, height: 30)
            .background(Color.gray)
            // Viewを実際に移動させる
            .offset(x: position.x + dragOffset.width, y: position.y + dragOffset.height)
            .gesture(
                DragGesture()
                    // ドラッグジェスチャーの変更を観測
                    .updating($dragOffset, body: { (value, state, _) in
                        // state(GestureState)に移動した値を格納することで変化中も移動する
                        state = value.translation
                    })
                    // ドラッグ終了した際に呼ばれる
                    .onEnded({ value in
                        // 移動が完了した座標を格納して固定
                        self.position.x += value.translation.width
                        self.position.y += value.translation.height
                    })
            )
    }
}
【Swift UI】@GestureStateの使い方!

バナーコンテナビューを実装する

GestureStateを使用して「バナーコンテナビューを実装」してみました。スワイプでスクロールできて、バナーごとに停止するようになっています。

【Swift UI】@GestureStateの使い方!
struct BannerContainerView: View {
    
    private let array = Array(1...30)
    private let deviceWidth = DeviceSizeUtility.deviceWidth
    @GestureState private var dragOffset: CGFloat = 0
    @State private var currentIndex: CGFloat = 0
    
    var body: some View {
        VStack(spacing: 0) {
            GeometryReader { geometry in
                LazyHStack(spacing: 0) {
                    ForEach(array, id: \.self) { value in
                        Text("\(value)")
                            .frame(width: deviceWidth, height: 80)
                            .background(.orange)
                            .overlay {
                                RoundedRectangle(cornerRadius: 8)
                                    .stroke()
                            }
                    }
                }
            }
            // スワイプ中にバナーコンテナを移動させるためのオフセット
            .offset(x: dragOffset)
            // スワイプ完了後にバナーコンテナ自体を移動した後に固定するためのオフセット
            .offset(x: -(currentIndex * deviceWidth))
            // スワイプ完了後の動作をなめらかにするためのアニメーション
            .animation(.interpolatingSpring(mass: 0.6, stiffness: 150, damping: 80, initialVelocity: 0.1), value: dragOffset)
            .gesture(
                DragGesture(minimumDistance: 0)
                    // スワイプの変化を観測しスワイプの変化分をHStackのoffsetに反映(スワイプでビューが動く部分を実装)
                    .updating(self.$dragOffset, body: { (value, state, _) in
                        // スワイプ変化量をdragOffsetに反映
                        state = value.translation.width
                        // スワイプが完了するとdragOffsetの値は0になる
                        print("START", dragOffset)
                    })
                    .onEnded { value in
                        // value.translation.width:ドラッグを離した時の最終的な移動距離
                        // 1なら左スワイプ
                        // 0なら右スワイプ
                        let newIndex: CGFloat = value.translation.width > 0 ? 1 : 0
                        if newIndex == 1 {
                            currentIndex = max(currentIndex - 1, 0)
                            print("右スワイプ", currentIndex)
                        } else {
                            currentIndex = min(currentIndex + 1, CGFloat(array.count))
                            print("左スワイプ", currentIndex)
                        }
                      
                        print("END", dragOffset)
                    }
            )
        }
    }
}

まだまだ勉強中ですので間違っている点や至らぬ点がありましたら教えていただけると助かります。

ご覧いただきありがとうございました。

searchbox

スポンサー

ProFile

ame

趣味:読書,プログラミング学習,サイト制作,ブログ

IT嫌いを克服するためにITパスを取得しようと勉強してからサイト制作が趣味に変わりました笑
今はCMSを使わずこのサイトを完全自作でサイト運営中〜

New Article